﻿using JNPF.Basics.Logic;
using JNPF.IM.Models.Dtos;
using JNPF.Utils;
using Microsoft.AspNetCore.Builder;
using System;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace JNPF.IMServe
{
    public static class WebSocketHelper
    {
        private static IMContentBll iMContentBll = new IMContentBll();
        private static MessageBll messageBll = new MessageBll();

        /// <summary>
        /// MapWebSocketManager
        /// </summary>
        /// <param name="app"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public static void MapWebSocketManager(this IApplicationBuilder app, string path = "")
        {
            app.Use(async (context, next) =>
            {
                if (context.Request.Path == path)
                {
                    if (!context.WebSockets.IsWebSocketRequest)
                        context.Response.StatusCode = 400;
                    else
                        await ProcessWebSocket(context, await context.WebSockets.AcceptWebSocketAsync());
                }
                else
                {
                    await next();
                }
            });
        }
        private static async Task ProcessWebSocket(Microsoft.AspNetCore.Http.HttpContext context, WebSocket socket)
        {
            var connectionId = context.Request.Headers["Sec-WebSocket-Key"];
            var userInfo = new UserInfo();
            try
            {
                if (socket.State == WebSocketState.Open)
                {
                    Console.WriteLine(DateTime.Now.ToString("yyyy/M/d HH:mm:ss") + " WebSocket - OnOpen:" + connectionId);
                }
                CancellationToken ct = context.RequestAborted;
                while (true)
                {
                    if (ct.IsCancellationRequested)
                        break;
                    string receivedResult = await ReceiveAsync(socket, ct);
                    if (string.IsNullOrEmpty(receivedResult))
                    {
                        if (socket.State != WebSocketState.Open)
                            break;
                        continue;
                    }
                    if (socket.State == WebSocketState.Open)
                    {
                        var receivedMessage = receivedResult.ToObject();
                        var receivedMethod = receivedMessage["method"].ToString();
                        var receivedToken = receivedMessage["token"].ToString();
                        if (!string.IsNullOrEmpty(receivedToken))
                        {
                            RequestAuthorize.Token = receivedToken;
                            userInfo = UserProvider.Instance.Get(receivedToken);
                            if (userInfo == null)
                            {
                                SendAsync(socket, new { method = "logout" }.ToJson());
                            }
                        }
                        if (userInfo != null)
                        {
                            switch (receivedMethod)
                            {
                                case "OnConnection":
                                    #region 建立连接
                                    var isMobileDevice = receivedMessage["mobileDevice"].ToBool();//app-true, PC-false
                                                                                                  //Console.WriteLine("UserAccount：" + userInfo.UserAccount);
                                    OnlineUserProvider.OnlineUserList.Add(new OnlineUserModel { ConnectionId = connectionId, UserId = userInfo.UserId, TenantId = userInfo.TenantId, IsMobileDevice = isMobileDevice, WebSocket = socket });
                                    lock (OnlineUserProvider.locker)
                                    {
                                        var onlineUserList = OnlineUserProvider.OnlineUserList.FindAll(q => q.TenantId == userInfo.TenantId && q.IsMobileDevice == isMobileDevice);


                                        #region 反馈信息给登录者
                                        var onlineUsers = onlineUserList.Select(m => m.UserId).ToList();
                                        var webOnlineUsers = OnlineUserProvider.OnlineUserList.FindAll(q => q.TenantId == userInfo.TenantId && q.IsMobileDevice == false).Select(m => m.UserId).ToList();
                                        foreach (var item in webOnlineUsers)
                                        {
                                            if (!onlineUsers.Contains(item))
                                                onlineUsers.Add(item);
                                        }
                                        var unreadNums = iMContentBll.GetUnreadList(userInfo.UserId).Result;
                                        var unreadNoticeCount = messageBll.GetUnreadNoticeCount(userInfo.UserId).Result;
                                        var unreadMessageCount = messageBll.GetUnreadMessageCount(userInfo.UserId).Result;
                                        var noticeDefaultText = messageBll.GetInfoDefault(1).Result.IsEmpty() ? "" : messageBll.GetInfoDefault(1).Result.Title;
                                        var messageDefaultText = messageBll.GetInfoDefault(2).Result.IsEmpty() ? "" : messageBll.GetInfoDefault(1).Result.Title;
                                        SendAsync(socket, new { method = "initMessage", onlineUsers, unreadNums, unreadNoticeCount, noticeDefaultText, unreadMessageCount, messageDefaultText }.ToJson());
                                        #endregion

                                        #region 通知所有在线用户，有用户在线
                                        if (!webOnlineUsers.Contains(userInfo.UserId))
                                        {
                                            foreach (var item in onlineUserList)
                                            {
                                                SendAsync(item.WebSocket, new { method = "online", userId = userInfo.UserId }.ToJson());
                                            }
                                        }
                                        messageBll = new MessageBll(onlineUserList.Select(x => x.WebSocket).ToList());
                                        #endregion
                                    }
                                    #endregion
                                    break;
                                case "SendMessage":
                                    #region 发送消息
                                    var toUserId = receivedMessage["toUserId"].ToString();
                                    var messageType = receivedMessage["messageType"].ToString(); //text/voice/image
                                    var messageContent = receivedMessage["messageContent"].ToString();
                                    //var messageId = receivedMessage["messageId"].ToString();
                                    var fileName = "";
                                    if (messageType != "text")
                                    {
                                        var directoryPath = ConfigurationKey.IMContentFilePath;
                                        if (!Directory.Exists(directoryPath))
                                            Directory.CreateDirectory(directoryPath);
                                        fileName = CommonHelper.Guid + (messageType == "voice" ? ".mp3" : ".png");
                                        var filePath = directoryPath + fileName;
                                        var fileBase64 = receivedMessage["messageContent"]["base64"].ToString().Trim().Replace("%", "").Replace(",", "").Replace(" ", "+");
                                        var isOk = Base64ToFileAndSave(filePath, fileBase64);
                                        if (isOk == false)
                                            LogFactory.GetLogger().Error(fileBase64);
                                        if (isOk == true && messageType == "image")
                                            FileHelper.MakeThumbnail(filePath, (directoryPath + "T" + fileName), 300, 300, "H", Path.GetExtension(fileName), 0, 0);
                                    }
                                    var onlineUser = OnlineUserProvider.OnlineUserList.FirstOrDefault(q => q.ConnectionId == connectionId);
                                    var onlineToUser = OnlineUserProvider.OnlineUserList.FirstOrDefault(q => q.TenantId == onlineUser.TenantId && q.UserId == toUserId && q.IsMobileDevice == onlineUser.IsMobileDevice);
                                    if (onlineUser != null)
                                    {
                                        #region saveMessage
                                        if (messageType == "text")
                                        {
                                            await iMContentBll.SendMessage(onlineUser.UserId, toUserId, messageContent, messageType);
                                        }
                                        else if (messageType == "image")
                                        {
                                            var toMessage = new { path = fileName, width = receivedMessage["messageContent"]["width"].ToInt(), height = receivedMessage["messageContent"]["height"].ToInt() };
                                            await iMContentBll.SendMessage(onlineUser.UserId, toUserId, toMessage.ToJson(), messageType);
                                        }
                                        else if (messageType == "voice")
                                        {
                                            var toMessage = new { path = fileName, length = receivedMessage["messageContent"]["length"].ToString() };
                                            await iMContentBll.SendMessage(onlineUser.UserId, toUserId, toMessage.ToJson(), messageType);
                                        }
                                        #endregion

                                        #region sendMessage
                                        if (messageType == "text")
                                        {
                                            SendAsync(socket, new { method = "sendMessage", onlineUser.UserId, messageType, toUserId, toMessage = messageContent, dateTime = DateTime.Now }.ToJson());
                                        }
                                        else if (messageType == "image")
                                        {
                                            var toMessage = new { path = fileName, width = receivedMessage["messageContent"]["width"].ToInt(), height = receivedMessage["messageContent"]["height"].ToInt() };
                                            SendAsync(socket, new { method = "sendMessage", onlineUser.UserId, messageType, toUserId, toMessage, dateTime = DateTime.Now }.ToJson());
                                        }
                                        else if (messageType == "voice")
                                        {
                                            var toMessage = new { path = fileName, length = receivedMessage["messageContent"]["length"].ToString() };
                                            SendAsync(socket, new { method = "sendMessage", onlineUser.UserId, messageType, toUserId, toMessage, dateTime = DateTime.Now }.ToJson());
                                        }
                                        #endregion
                                    }
                                    if (onlineToUser != null)
                                    {
                                        #region receiveMessage
                                        if (messageType == "text")
                                        {
                                            SendAsync(onlineToUser.WebSocket, new { method = "receiveMessage", messageType, formUserId = onlineUser.UserId, formMessage = messageContent, dateTime = DateTime.Now }.ToJson());
                                        }
                                        else if (messageType == "image")
                                        {
                                            var formMessage = new { path = fileName, width = receivedMessage["messageContent"]["width"].ToInt(), height = receivedMessage["messageContent"]["height"].ToInt() };
                                            SendAsync(onlineToUser.WebSocket, new { method = "receiveMessage", messageType, formUserId = onlineUser.UserId, formMessage, dateTime = DateTime.Now }.ToJson());
                                        }
                                        else if (messageType == "voice")
                                        {
                                            var formMessage = new { path = fileName, length = receivedMessage["messageContent"]["length"].ToString() };
                                            SendAsync(onlineToUser.WebSocket, new { method = "receiveMessage", messageType, formUserId = onlineUser.UserId, formMessage, dateTime = DateTime.Now }.ToJson());
                                        }
                                        #endregion
                                    }
                                    #endregion
                                    break;
                                case "UpdateReadMessage":
                                    #region 更新已读
                                    var formUserId = receivedMessage["formUserId"].ToString();
                                    onlineUser = OnlineUserProvider.OnlineUserList.FirstOrDefault(q => q.ConnectionId == connectionId);
                                    if (onlineUser != null)
                                    {
                                        await iMContentBll.ReadMessage(formUserId, onlineUser.UserId);
                                    }
                                    #endregion
                                    break;
                                case "MessageList":
                                    #region 消息列表
                                    var sendUserId = receivedMessage["toUserId"].ToString();                //发送者
                                    var receiveUserId = receivedMessage["formUserId"].ToString();           //接收者
                                    var requestParam = new PageInput();
                                    requestParam.currentPage = receivedMessage["currentPage"].ToInt();
                                    requestParam.pageSize = receivedMessage["pageSize"].ToInt();
                                    requestParam.sord = receivedMessage["sord"].ToString();
                                    var data = await iMContentBll.GetMessageList(sendUserId, receiveUserId, requestParam);
                                    var output = new MessageListOutput()
                                    {
                                        list = data.list.Select(x => new MessageListModel()
                                        {
                                            content = x.Content,
                                            contentType = x.ContentType,
                                            id = x.Id,
                                            receiveTime = CommonHelper.TimeToTimeStamp(x.ReceiveTime.ToDate()),
                                            receiveUserId = x.ReceiveUserId,
                                            sendTime = CommonHelper.TimeToTimeStamp(x.SendTime.ToDate()),
                                            sendUserId = x.SendUserId,
                                            state = x.State
                                        }).ToList().OrderBy(x => x.sendTime),
                                        pagination = data.pagination,
                                        method = "messageList"
                                    };
                                    SendAsync(socket, output.ToJson());
                                    #endregion
                                    break;
                                default:
                                    break;
                            }
                        }

                    }
                }
                if (socket.State != WebSocketState.Open)
                {
                    Console.WriteLine(DateTime.Now.ToString("yyyy/M/d HH:mm:ss") + " WebSocket - OnClose:" + connectionId);
                    if (userInfo != null)
                        Console.WriteLine("UserAccount:" + userInfo.UserAccount);
                    else
                        Console.WriteLine("UserAccount:");

                    #region 关闭连接
                    lock (OnlineUserProvider.locker)
                    {
                        var onlineUser = OnlineUserProvider.OnlineUserList.FirstOrDefault(q => q.ConnectionId == connectionId);
                        if (onlineUser != null)
                        {
                            OnlineUserProvider.OnlineUserList.Remove(onlineUser);

                            #region 通知所有在线用户，有用户离线 
                            var webOnlineUsers = OnlineUserProvider.OnlineUserList.FindAll(q => q.TenantId == userInfo.TenantId && q.IsMobileDevice == false).Select(m => m.UserId).ToList();
                            if (!webOnlineUsers.Contains(onlineUser.UserId))
                            {
                                var onlineUserList = OnlineUserProvider.OnlineUserList.FindAll(q => q.TenantId == onlineUser.TenantId);
                                foreach (var item in onlineUserList)
                                {
                                    if (item.WebSocket != null)
                                    {
                                        SendAsync(item.WebSocket, new { method = "offline", userId = onlineUser.UserId }.ToJson());
                                    }
                                }
                            }
                            #endregion
                        }
                    }
                    #endregion

                    socket.Dispose();
                }
            }
            catch (Exception ex)
            {
                LogFactory.GetLogger().Error(ex);
                Console.WriteLine(DateTime.Now.ToString("yyyy/M/d HH:mm:ss") + " WebSocket - OnError\r\n");
                if (userInfo != null)
                {
                    var onlineUser = OnlineUserProvider.OnlineUserList.FirstOrDefault(q => q.ConnectionId == connectionId);
                    if (onlineUser != null)
                        OnlineUserProvider.OnlineUserList.Remove(onlineUser);
                    Console.WriteLine("TenantId:" + userInfo.TenantId);
                    Console.WriteLine("UserAccount:" + userInfo.UserAccount);
                }
            }
        }
        private static void SendAsync(WebSocket socket, string message, CancellationToken ct = default(CancellationToken))
        {
            try
            {
                var buffer = Encoding.UTF8.GetBytes(message);
                var segment = new ArraySegment<byte>(buffer);
                socket.SendAsync(segment, WebSocketMessageType.Text, true, ct);
            }
            catch (Exception ex)
            {
                LogFactory.GetLogger().Error("SendAsync - OnError");
                LogFactory.GetLogger().Error(ex);
                throw;
            }
        }
        private static async Task<string> ReceiveAsync(WebSocket socket, CancellationToken ct = default(CancellationToken))
        {
            try
            {
                var buffer = new ArraySegment<byte>(new byte[8192]);
                using (var ms = new MemoryStream())
                {
                    WebSocketReceiveResult result;
                    do
                    {
                        ct.ThrowIfCancellationRequested();
                        result = await socket.ReceiveAsync(buffer, ct);
                        ms.Write(buffer.Array, buffer.Offset, result.Count);
                    }
                    while (!result.EndOfMessage);
                    ms.Seek(0, SeekOrigin.Begin);
                    if (result.MessageType != WebSocketMessageType.Text)
                    {
                        return null;
                    }
                    using (var reader = new StreamReader(ms, Encoding.UTF8))
                    {
                        return await reader.ReadToEndAsync();
                    }
                }
            }
            catch (Exception ex)
            {
                LogFactory.GetLogger().Error("ReceiveAsync - OnError");
                LogFactory.GetLogger().Error(ex);
                throw;
            }
        }
        private static bool Base64ToFileAndSave(string fileName, string strInput)
        {
            fileName = fileName.Replace("/", "\\");
            bool bTrue = false;
            try
            {
                byte[] buffer = Convert.FromBase64String(strInput);
                FileStream fs = new FileStream(fileName, FileMode.CreateNew);
                fs.Write(buffer, 0, buffer.Length);
                fs.Close();
                bTrue = true;
            }
            catch (Exception ex)
            {
                LogFactory.GetLogger().Error("Base64ToFileAndSave - OnError");
                LogFactory.GetLogger().Error(ex);
                bTrue = false;
            }
            return bTrue;
        }
    }
}
